﻿/*
 *多线程视频批量转换
 *ver:v1.0
 *author:liujg
 *introduction:参考目录中的App.config进行配置
 *lately update:2011.11.02
 */
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using Common.Logging;

namespace Baby.VideoConvertor
{
    public class FileWatcher
    {
        static NameValueCollection AppSettings { get { return ConfigurationManager.AppSettings; } }
        static readonly Dictionary<string, string> FilterType;
        static readonly Queue<string> SourceQueue = new Queue<string>();
        static readonly Hashtable ConvertingHt = new Hashtable();
        static System.Timers.Timer _timerQueue;
        static System.Timers.Timer _timerThread;
        static int _activeThreadCount = 0;
        static ILog _log;
        static bool _killMarker = false;
        //源文件目录
        static readonly string _srcDir;
        //转换工作目录
        static readonly string _workDir;
        //目标文件存放目录
        static readonly string _targetDir;
        //目标文件类型
        static readonly string _targetType;

        static readonly int _threadSize;

        static FileWatcher()
        {
            _srcDir = AppSettings["SourceDirectory"];
            _workDir = AppSettings["WorkDirectory"];
            _targetDir = AppSettings["TargetDirectory"];
            _targetType = AppSettings["TargetType"];
            _threadSize = int.Parse(AppSettings["ThreadSize"]);
            FilterType = new Dictionary<string, string>();
            foreach (var type in AppSettings["FileType"].Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
                FilterType.Add(type.ToLower(), type.ToLower());
            }
            _log = LogManager.GetCurrentClassLogger();
        }

        /// <summary>
        /// 开启转换监控
        /// </summary>
        public static void Begin()
        {
            var sb = new StringBuilder();
            sb.Append("------------Start convert service------------\r\n");
            var directoryInfo = new DirectoryInfo(_srcDir);

            if (!directoryInfo.Exists)
            {
                sb.AppendFormat("the source directory \"{0}\" not exists!\r\n", _srcDir);
                return;
            }

            if (string.Compare(_srcDir, _workDir, true) == 0)
            {
                sb.Append("源目录和工作目录不能配置为相同目录!");
                return;
            }

            _log.Debug(sb.ToString());
            StartQueueWatch();
            StartThreadWatch();
            CheckUncompleted();
        }

        /// <summary>
        /// 结束转换监控
        /// </summary>
        public static void Stop()
        {
            _killMarker = true;
            var processes = Process.GetProcessesByName("ffmpeg");
            for (var i = 0; i < processes.Length; i++)
            {
                try
                {
                    processes[i].Kill();
                }
                catch { }
            }
            _log.Debug(string.Format("------------Exit convert service({0} ffmpegs in running)------------", processes.Length));
        }

        //查找未完成文件
        static void CheckUncompleted()
        {
            var sb = new StringBuilder();
            var workDirInfo = new DirectoryInfo(_workDir);
            int m = 0, n = 0, z = 0;
            if (!workDirInfo.Exists)
            {
                return;
            }
            //删除临时文件
            var tempFiles = workDirInfo.GetFiles("*.temp.flv");
            foreach (var tempFile in tempFiles)
            {
                if (!IsUsed(tempFile.FullName))
                {
                    tempFile.Delete();
                    n++;
                }
                m++;
            }
            sb.AppendFormat("查找异常临时文件{0}个,清除{1}个;", m, n);

            m = n = z = 0;
            //移动未完成到源目录
            var workingFiles = workDirInfo.GetFiles();
            foreach (var workingFile in workingFiles)
            {
                if (!FilterType.ContainsKey(workingFile.Extension.ToLower()) || workingFile.Name.Contains("temp.flv") || IsUsed(workingFile.FullName))
                {
                    continue;
                }

                var srcFullName = Path.Combine(_srcDir, workingFile.Name);
                //如果源目录存在相同文件则删除否则移动到源目录
                if (File.Exists(srcFullName))
                {
                    workingFile.Delete();
                    z++;
                }
                else
                {
                    workingFile.CopyTo(srcFullName);
                    workingFile.Delete();
                    n++;
                }
                m++;
            }
            sb.AppendFormat("异常源文件{0}个,恢复{1}个,删除{2}个同名文件.", m, n, z);
            _log.Debug(sb.ToString());
        }

        //启动队列监视器
        static void StartQueueWatch()
        {
            var timeSpan = 7000;
            _timerQueue = new System.Timers.Timer(timeSpan);
            _timerQueue.AutoReset = true;
            _timerQueue.Elapsed += TimerQueueElapsed;
            _timerQueue.Start();
            _log.Debug(string.Format("Start queue watch,pre {0}ms scan", timeSpan));
        }

        //监视队列加载文件到转换队列
        static void TimerQueueElapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            var filesInfo = new DirectoryInfo(_srcDir).GetFiles();
            var files = new List<string>();
            var converting = Hashtable.Synchronized(ConvertingHt);
            foreach (var file in filesInfo)
            {
                //跳过不存在的文件,非指定的文件类型,已存在队列中的文件,同名正在转换的文件,正在使用的文件
                if (!file.Exists || !FilterType.ContainsKey(file.Extension.ToLower()) || SourceQueue.Contains(file.FullName) || converting.ContainsKey(file.FullName) || IsUsed(file.FullName))
                {
                    continue;
                }

                files.Add(file.FullName);
            }

            //将文件路径添加到队列
            if (files.Count > 0)
            {
                var sb = new StringBuilder();
                sb.AppendFormat("加载{0}个文件到队列：\r\n", files.Count);
                lock (SourceQueue)
                {
                    for (var i = 0; i < files.Count; i++)
                    {
                        var file = files[i];
                        SourceQueue.Enqueue(file);
                        sb.AppendFormat("{0}.\t'{1}'\r\n", i + 1, file);
                    }
                }
                sb.AppendFormat("The queue contains {0} files,Active-threads:{1}", SourceQueue.Count, _activeThreadCount);
                _log.Debug(sb.ToString());
            }
        }

        //启动线程监视器
        static void StartThreadWatch()
        {
            var timeSpan = 5000;
            _timerThread = new System.Timers.Timer(timeSpan);
            _timerThread.AutoReset = true;
            _timerThread.Elapsed += TimerThreadElapsed;
            _timerThread.Start();
            _log.Debug(string.Format("Start thread watch,pre {0}ms scan", timeSpan));
        }

        //监视线程
        static void TimerThreadElapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            //如果队列中没有等待处理的视频则跳出
            var queueLen = SourceQueue.Count;
            if (queueLen == 0)
            {
                return;
            }
            _activeThreadCount = _activeThreadCount < 0 ? 0 : _activeThreadCount;
            var freeThreadCount = _threadSize - _activeThreadCount;

            if (queueLen <= freeThreadCount)
            {
                freeThreadCount = queueLen;
            }

            var threads = new Thread[freeThreadCount];
            for (var i = 0; i < threads.Length; i++)
            {
                var thread = new Thread(Proc);
                var name = Guid.NewGuid().ToString("N");
                thread.Name = string.Format("VThread_{0}", name.Substring(28));
                threads[i] = thread;
                Interlocked.Increment(ref _activeThreadCount);
            }

            for (var i = 0; i < threads.Length; i++)
            {
                threads[i].Start();
            }
        }

        //处理转换的线程方法
        static void Proc()
        {
            //如果队列为空或没有等待处理的文件,则结束线程当前线程
            if (SourceQueue.Count <= 0)
            {
                StopThread("队列为空，转换完成");
                return;
            }

            //创建转换实例
            var videoHandle = new VideoHandle(_workDir);
            //若转换工具未就绪则结束线程
            if (!videoHandle.IsReady)
            {
                StopThread("程序未就绪");
                return;
            }

            string currFile;
            //从待处理队列中获取一个的文件
            lock (SourceQueue)
            {
                currFile = SourceQueue.Dequeue();
            }

            //将将要处理的文件记录到正在处理的散列表
            lock (ConvertingHt.SyncRoot)
            {
                ConvertingHt.Add(currFile, currFile);
            }

            //源文件的信息实例
            var file = new FileInfo(currFile);
            //源文件名
            var fileName = file.Name;
            //构建工作源文件路径
            var workingFileFullName = Path.Combine(_workDir, fileName);

            _log.Debug(string.Format("线程:{0} 处理:{1} 剩余任务:{2}", Thread.CurrentThread.Name, fileName, SourceQueue.Count));

            if (file.Exists)
            {
                //如果源文件在工作目录不存在则移到工作目录
                if (!File.Exists(workingFileFullName))
                {
                    file.CopyTo(workingFileFullName);
                    file.Delete();
                }
                //如果工作文件正在使用则终止当前线程
                else if (IsUsed(workingFileFullName))
                {
                    StopThread(string.Format("工作目录下存在同名文件{0}且文件正在使用.", fileName));
                    return;
                }
                //如果工作文件名同源文件名相同则删除
                else
                {
                    File.Delete(workingFileFullName);
                    StopThread(string.Format("工作目录下的同名文件{0},已删除.", fileName));
                    return;
                }
            }
            else
            {
                StopThread(string.Format("源文件{0}不存在.", fileName));
                return;
            }

            //目标文件的完全限定路径
            var outputFile = Path.Combine(_targetDir, fileName.Substring(0, fileName.Length - file.Extension.Length) + _targetType);
            //开始转换
            var watch = new Stopwatch();
            watch.Start();
            var convertResult = videoHandle.Convert(workingFileFullName, outputFile, ref _killMarker);
            watch.Stop();

            if (convertResult == ConvertResult.BeStoped)
            {
                StopThread("用户终止");
                return;
            }

            //删除工作目录中的源文件
            if (File.Exists(workingFileFullName))
            {
                File.Delete(workingFileFullName);
            }

            //将处理完成的文件信息从记录正在处理的文件的散列表移除
            lock (ConvertingHt.SyncRoot)
            {
                ConvertingHt.Remove(currFile);
            }
            _log.Debug(string.Format("线程:{0} 完成:{1} 结果:{2} 耗时:{3}ms", Thread.CurrentThread.Name, fileName, convertResult, watch.ElapsedMilliseconds));

            //递归检查下一个待处理文件
            Proc();
        }

        //终止当前线程
        static void StopThread(string msg)
        {
            Interlocked.Decrement(ref _activeThreadCount);
            _log.Debug(string.Format("线程:{0} 退出,备注:{1}", Thread.CurrentThread.Name, msg));
            Thread.CurrentThread.Abort();
        }

        //判断文件是否在使用中
        static bool IsUsed(string path)
        {
            FileStream fs = null;
            var isUsed = false;
            try
            {
                fs = File.Open(path, FileMode.Append, FileAccess.Write);
            }
            catch
            {
                isUsed = true;
            }

            if (fs != null)
            {
                fs.Close();
            }
            return isUsed;
        }

        //文件比较
        static bool FileEquals(string file1, string file2)
        {

            var fileInfo1 = new FileInfo(file1);
            var fileInfo2 = new FileInfo(file2);

            if (!fileInfo1.Exists || !fileInfo2.Exists)
            {
                return false;
            }

            var fs1 = fileInfo1.Open(FileMode.Open, FileAccess.Read);
            var fs2 = fileInfo2.Open(FileMode.Open, FileAccess.Read);

            var md5 = MD5.Create();
            var hashVal1 = md5.ComputeHash(fs1);
            var hashVal2 = md5.ComputeHash(fs2);
            var md5Val1 = BitConverter.ToString(hashVal1).Replace("-", string.Empty);
            var md5Val2 = BitConverter.ToString(hashVal2).Replace("-", string.Empty);
            fs1.Close();
            fs2.Close();

            return string.Equals(md5Val1, md5Val2);
        }
    }
}